<html><head><meta charset="utf-8"><title>实战：使用 Express 和中间件来实现 Webpack-dev-server-慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">

<div class="container clearfix" id="top" style="display: block; width: 1134px;">
    
    <div class="center_con js-center_con l" style="width: 1134px;">
        <div class="article-con">
                            <!-- 买过的阅读 -->
                <div class="map">
                    <a href="/read" target="_blank"><i class="imv2-feather-o"></i></a>
                    <a href="/read/29" target="_blank">Webpack 从零入门到工程化实战</a>
                    <a href="" target="_blank">
                        <span>
                            / 5-4 实战：使用 Express 和中间件来实现 Webpack-dev-server
                        </span>
                    </a>
                </div>

            


            <div class="art-title" style="margin-top: 0px;">
                实战：使用 Express 和中间件来实现 Webpack-dev-server
            </div>
            <div class="art-info">
                
                <span>
                    更新时间：2019-07-15 10:24:04
                </span>
            </div>
            <div class="art-top">
                                <img src="https://img2.mukewang.com/5cd964ce0001639e06400359.jpg" alt="">
                                                <div class="famous-word-box">
                    <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                    <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                    <div class="famous-word">世界上最快乐的事，莫过于为理想而奋斗。<p class="author">——苏格拉底</p></div>
                </div>
                            </div>
            <div class="art-content js-lookimg">
                <div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在原理篇介绍<a href="TODO">HMR</a>实现的文章中，我们对 Webpack-dev-server 和 HMR 做了深入的剖析。在实际开发中，我们仅仅使用 Webpack-dev-server 功能可能不够，例如：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;">不能监控 Webpack 配置文件更改之后重新启动；</li>
<li style="font-size: 20px; line-height: 38px;">对于本身就是 Node.js 做后端服务器的项目来说，webpack-dev-server 反而会因为不能跟原服务器结合显得很鸡肋；</li>
<li style="font-size: 20px; line-height: 38px;">只对 Node.js 有支持，如果后端程序是 PHP、Java 写的那么 webpack-dev-server 就束手无策。</li>
</ul>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">webpack-dev-server 本质上是一个 Expressjs 的服务器，而真正跟 Webpack 交互的是它用到的中间件——webpack-dev-middleware，如果我们前端项目本身就是一个 Express 服务器，那么我们可以使用 webpack-dev-middleware 和 webpack-hot-middleware 实现 webpack-dev-server 的功能，webpack-hot-middleware 这个 Express 中间件可以为 Express 服务器提供 LiveReload 功能。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本文将从头带大家零基础用 Express 来实现一个 Webpack-dev-server，总共代码行数带注释不过 150 行左右，却实现了 webpack-dev-server 的主要功能，并且添加 mock 功能，还能够实现 Webpack 配置文件 watch 功能。</p>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：webpack-dev-middleware 和 webpack-hot-middleware 的功能，还有 Koa 版本和 Hapi 版本，Koa 和 Hapi 都是 Node.js 服务器框架。</p>
</blockquote>
</div><div class="cl-preview-section"><h2 id="express-核心概念" style="font-size: 30px;">Express 核心概念</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><a href="http://expressjs.com/">Express</a>是一个流行的 Node.js 服务端框架，通过 Express 可以创建 Node.js 服务端程序。Express API 简单，功能却很强大，很多流行的 Web 应用开发框架都是基于 Express 来实现的，包括我们公司在内的很多公司 Web 产品后台服务底层也是使用 Express 框架来实现的，所以 Express 是可以用于线上生产环境的。</p>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：为了后面内容实际操作，可以先创建一个<code>dev-server</code>的文件夹，并且执行<code>npm init -y</code>准备好 NPM 环境，然后安装 Express 包：<code>npm i -S express</code>。后续的代码都是在这个文件夹下编写对应的代码。</p>
</blockquote>
</div><div class="cl-preview-section"><h3 id="router">Router</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">路由（Router）是一种将 URL 和 HTTP 方法映射到特定处理回调函数的技术，例如我们希望访问<code>/hello</code>这个 URL 地址，就显示<code>hi, world</code>文字，那么我们在 Express 中可以这样来实现：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> http <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'/hello'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>request<span class="token punctuation">,</span> response<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    response<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'hi, world'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

http<span class="token punctuation">.</span><span class="token function">createServer</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在上面的代码中，<code>app.get('url', callback)</code>形式就是一个<code>GET</code>路由，将<code>callback</code>和<code>URL</code>进行了映射绑定。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 Express 中封装了多种 HTTP 请求方式，我们主要用到的是 GET 和 POST 两种，即<code>app.get()</code>和<code>app.post()</code>。它们的第一个参数都是一个请求路径，第二个参数则为处理请求的回调函数。回调函数有两个参数，分别是<code>request</code>和<code>response</code>，即对应 HTTP 协议中的请求和响应两个概念。</p>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：Express 的路由除了纯字符串这类，还支持正则、字符串通配符、命名参数等，但是应该注意路由的解析速度，如果一个正则路由写的匹配极低，那么会影响整个 Server 应用速度的。</p>
</blockquote>
</div><div class="cl-preview-section"><h3 id="request-和-response">Request 和 Response</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">Request 和 Response 分别对应着 HTTP 协议中的请求和响应。在 Express 中，将跟 HTTP 请求相关的变量都放到了 Request 对象中，例如：我们可以从 Request 对象中读取用户的浏览器 UserAgent、用户的 IP 地址、用户携带的 Cookie 信息等。Response 对象则主要提供跟做出 HTTP 响应相关的函数和属性，比如设置响应内容、设置 HTTP 响应头中的设置 Cookie、设置 HTTP 状态码等。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">下面的一段代码，用户访问<code>http://localhost:3000/</code>则显示用户的浏览器 UserAgent：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> http <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>req<span class="token punctuation">,</span> res<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    res<span class="token punctuation">.</span><span class="token function">writeHead</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token string">'Content-Type'</span><span class="token punctuation">:</span> <span class="token string">'text/plain'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'user-agent'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

http<span class="token punctuation">.</span><span class="token function">createServer</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h3 id="中间件">中间件</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">中间件是 Express 中最大的特性之一。我们可以将中间件看成由一串回调函数组成的回调栈，每个回调函数都会接受<code>request</code>、<code>response</code>和<code>next</code>参数，我们可以在各个回调函数中做不同的事情，例如专门记录请求日志的回调函数、处理 Cookie 的回调函数、专门处理 HTTP 请求头的回调函数、专门做错误页面展现的回调函数…通过一个 HTTP 请求（request）经过回调栈最终对 HTTP 做出响应（response）。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 Express 中，可以使用<code>Express.use</code>方法给一个 server 添加中间件：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> http <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> cuid <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'cuid'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 日志记录中间件</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> response<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'In comes a '</span> <span class="token operator">+</span> request<span class="token punctuation">.</span>method <span class="token operator">+</span> <span class="token string">' to '</span> <span class="token operator">+</span> request<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 我们还可以修改request或者response对象，让他们携带每次请求的唯一数据</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> response<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    request<span class="token punctuation">.</span>id <span class="token operator">=</span> <span class="token function">cuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 传递至下一个中间件</span>
    <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 发送实际响应</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> response<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    response<span class="token punctuation">.</span><span class="token function">writeHead</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token string">'Content-Type'</span><span class="token punctuation">:</span> <span class="token string">'text/plain'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 这里最后没有调用next扔给下一个中间件，而是直接输出了响应内容</span>
    response<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">'Hello, world! Your ID is '</span> <span class="token operator">+</span> request<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

http<span class="token punctuation">.</span><span class="token function">createServer</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：在编写 Node.js 的 Server 端应用一定要做好内存管理。在浏览器内，每个用户访问同一个页面时都是一个独立的浏览器 Tab 甚至是独立的设备，所以内存使用不当造成内存泄漏的问题也不会特别严重，而在 Node.js Server 应用中，成千上万的用户会同时访问我们的服务，这时候应该注意：1. 不同的用户数据不能使用全局变量管理；2. 小的内存泄漏问题，因为请求多了执行次数增多而造成大的内存泄漏问题，所以开发 Node.js Server 应用一定要转变思想，不能依旧停留在浏览器开发模型中。在 Express 中，用户差异数据可以使用用户自己的<code>request</code>对象来存储，参考上面中间件示例中的<code>request.id</code>。</p>
</blockquote>
</div><div class="cl-preview-section"><h2 id="使用-express-及其中间件来实现-webpack-dev-server" style="font-size: 30px;">使用 Express 及其中间件来实现 Webpack-dev-server</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">通过之前的功能，我们了解到 Webpack-dev-server 主要功能有以下几点：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">静态服务器；</li>
<li style="font-size: 20px; line-height: 38px;">代理服务器，使用 http-proxy-middleware 实现；</li>
<li style="font-size: 20px; line-height: 38px;">实现 Mock Server；</li>
<li style="font-size: 20px; line-height: 38px;">使用 <a href="https://github.com/webpack/webpack-dev-middleware">webpack-dev-middleware</a> 实现跟 Webpack 交互；</li>
<li style="font-size: 20px; line-height: 38px;">使用 SockJS 实现跟<code>webpack/hot/server</code>通信，实现 HMR 功能。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">为了对标 Webpack-dev-server 的配置项，我们先设置一个 Webpack-dev-server 的配置文件，内容如下：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// devServer.config.js</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    port<span class="token punctuation">:</span> <span class="token number">3000</span><span class="token punctuation">,</span>
    contentBase<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
    <span class="token function">before</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
    hot<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    hotOnly<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
    proxy<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        <span class="token string">'/api'</span><span class="token punctuation">:</span> <span class="token string">'http://localhost:3000'</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">下面的 Express 来实现 dev-server 就是使用这个配置文件来做配置。</p>
</div><div class="cl-preview-section"><h3 id="使用-express-实现一个静态服务器">使用 Express 实现一个静态服务器</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 webpack-dev-server 中可以设置<code>contentBase</code>来做静态资源服务器。我们先用 Express 来实现一个静态资源服务器。在 Express 中要实现一个静态资源服务器可以使用 Express 内置的<a href="https://www.npmjs.com/package/serve-static">serve-static</a>这个中间件来实现：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// static-server.js</span>
<span class="token keyword">const</span> http <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 添加中间件，设置从`public`文件夹中查找文件</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>express<span class="token punctuation">.</span><span class="token keyword">static</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'public'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 最后，创建server，并且输出地址</span>
<span class="token keyword">const</span> server <span class="token operator">=</span> http<span class="token punctuation">.</span><span class="token function">createServer</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span>port<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Listening on %j'</span><span class="token punctuation">,</span> server<span class="token punctuation">.</span><span class="token function">address</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这时候我们在当前路径下创建个<code>public</code>文件夹，并且在里面添加个<code>hello.js</code>文件，然后执行<code>node static-server.js</code>，打开浏览器访问<a href="http://localhost:3000/hello.js">http://localhost:3000/hello.js</a>，就会看到 hello.js 的内容了！</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">就这么简单，6 行代码实现了一个静态资源服务器！</p>
</div><div class="cl-preview-section"><h3 id="添加代理服务器中间件">添加代理服务器中间件</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">现在我们已经知道 Webpack-dev-server 中使用的<code>devServer.proxy</code>实际是使用 Express 的中间件<a href="https://www.npmjs.com/package/http-proxy-middleware">http-proxy-middleware</a>来实现的了，那么我们首先在<code>package.json</code>中添加这个依赖：</p>
</div><div class="cl-preview-section"><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> -S http-proxy-middleware
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">然后按照 http-proxy-middleware 的文档，给我们刚才的代码添加 http-proxy-middleware 中间件：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js">Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>proxy<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>router <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 添加http-proxy-middleware</span>
    app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>router<span class="token punctuation">,</span> <span class="token function">httpProxyMiddleware</span><span class="token punctuation">(</span>proxy<span class="token punctuation">[</span>router<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h3 id="实现-mock-server-功能">实现 mock server 功能</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">对于前后端开发的项目，我们在绝大多数情况前后端开发人员会先约定接口格式，Web 前端可以按照约定接口格式使用本地的 Mock 数据进行开发，等后端接口准备就绪之后再进行使用后端接口进行联调。webpack-dev-server 提供了 proxy 配置。我们可以在开发中将接口代理到本地服务。一般我们的接口都是 JSON 格式的接口，但是 webpack-dev-server 使用的 http-proxy-middleware，已经不支持本地 JSON 文件的代理，这个 <a href="https://github.com/chimurai/http-proxy-middleware/issues/63">issue</a>说明了原因。我们在开发中的 Mock 需要自己使用中间件来实现。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在这里我们使用<a href="https://github.com/jaywcjlove/mocker-api">mocker-api</a>，我们先添加<code>mocker-api</code>：</p>
</div><div class="cl-preview-section"><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">npm</span> <span class="token function">install</span> -S mocker-api
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">然后按照 mocker-api 的文档添加中间件：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// 2. mocker-api</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>mock<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 这里的mock是mocker-api配置文件路径</span>
    <span class="token function">apiMockerMiddleware</span><span class="token punctuation">(</span>app<span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>mock<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><h3 id="整合-webpack">整合 Webpack</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">经过上面三步，我们的 Express 服务已经可以访问静态文件、做代理服务器、可以使用本地数据模拟 API 接口，下面要做的就是整合 Webpack 进我们的 Express 服务器，并且使用 webpack-dev-middleware 和 webpack-hot-middleware 来实现 HMR。</p>
</div><div class="cl-preview-section"><h4 id="webpack-hot-middleware-通信机制" style="font-size: 26px;">webpack-hot-middleware 通信机制</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">webpack-hot-middleware 的通信机制用的是<a href="https://developer.mozilla.org/zh-CN/docs/Server-sent_events/EventSource">EventSource</a>，EventsSource 的官方名称应该是 Server-sent events（缩写 SSE）服务端派发事件，EventSource 基于 HTTP 协议，它通过 HTTP 连接到一个服务器，以 text/event-stream 格式接收事件, 只是简单的单向通信，实现了服务端推送消息到客户端，而不能实现客户端发送数据到服务端。 虽然 EventSource 不能实现双向通信，但是在功能设计上它也有一些优点，比如：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">可以自动重连；</li>
<li style="font-size: 20px; line-height: 38px;">基于 HTTP 协议，可以引入<a href="https://libraries.io/search?platforms=NPM&amp;q=eventsource+polyfill">polyfill</a>支持低版本浏览器；</li>
<li style="font-size: 20px; line-height: 38px;">相对于 WebSocket 需要基于 TCP 设计一种新的通信协议，EventSource 更加轻量级一些。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在日常应用中，EventSource 因为受单向通信的限制只能用来实现像股票报价、新闻推送、实时天气这些只需要服务器发送消息给客户端场景中。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><strong>使用示例：</strong></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">首先支持 EventSource 的浏览器，存在<code>window.EventSource</code>，我们可以直接使用 EventSource 的 server 路径来实例化一个 EventSource 对象，然后可以绑定<code>open</code>、<code>message</code>、<code>error</code>等消息，还可以通过<code>addEventListener</code>方式监听具名消息：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// client.js</span>
<span class="token comment">// 实例化 EventSource 参数是服务端监听的路由</span>
<span class="token keyword">const</span> source <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventSource</span><span class="token punctuation">(</span><span class="token string">'/event-source-demo'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
source<span class="token punctuation">.</span><span class="token function-variable function">onopen</span> <span class="token operator">=</span> event <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 与服务器连接成功回调</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'成功与服务器连接'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// 监听从服务器发送来的所有没有指定事件类型的消息(没有event字段的消息)</span>
source<span class="token punctuation">.</span><span class="token function-variable function">onmessage</span> <span class="token operator">=</span> event <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 监听未命名事件</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'未命名事件'</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
source<span class="token punctuation">.</span><span class="token function-variable function">onerror</span> <span class="token operator">=</span> error <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 监听错误</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'错误'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// 监听指定类型的事件（可以监听多个）</span>
source<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'ping'</span><span class="token punctuation">,</span> event <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'ping'</span><span class="token punctuation">,</span> JSON<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在服务端，需要设置一个 EventSource 的路由，然后修改 HTTP 请求头的<code>'Content-Type': 'text/event-stream'</code>，最后按照 EventSource 规范发送 Response 响应内容即可：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// server.js</span>
<span class="token comment">// 使用Express框架，忽略之前代码</span>
<span class="token comment">// 监听event-source-demo路由服务端返回事件流</span>
app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'/event-source-demo'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>ewq<span class="token punctuation">,</span> res<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 根据 EventSource 规范设置报头</span>
    res<span class="token punctuation">.</span><span class="token function">writeHead</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
        <span class="token string">'Content-Type'</span><span class="token punctuation">:</span> <span class="token string">'text/event-stream'</span><span class="token punctuation">,</span> <span class="token comment">// 规定把报头设置为 text/event-stream</span>
        <span class="token string">'Cache-Control'</span><span class="token punctuation">:</span> <span class="token string">'no-cache'</span> <span class="token comment">// 设置不对页面进行缓存</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 用write返回事件流，事件流仅仅是一个简单的文本数据流，每条消息以一个空行(\n)作为分割。</span>
    res<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token string">':注释'</span> <span class="token operator">+</span> <span class="token string">'\n\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 注释行</span>
    res<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token string">'data:'</span> <span class="token operator">+</span> <span class="token string">'消息内容1'</span> <span class="token operator">+</span> <span class="token string">'\n\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 未命名事件</span>

    res<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>
        <span class="token comment">// 命名事件</span>
        <span class="token string">'event: ping'</span> <span class="token operator">+</span> <span class="token string">'\n'</span> <span class="token operator">+</span> <span class="token string">'data:'</span> <span class="token operator">+</span> <span class="token string">'消息内容2'</span> <span class="token operator">+</span> <span class="token string">'\n'</span> <span class="token operator">+</span> <span class="token string">'retry:'</span> <span class="token operator">+</span> <span class="token string">'2000'</span> <span class="token operator">+</span> <span class="token string">'\n'</span> <span class="token operator">+</span> <span class="token string">'id:'</span> <span class="token operator">+</span> <span class="token string">'12345'</span> <span class="token operator">+</span> <span class="token string">'\n\n'</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token comment">// 定时事件</span>
        res<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token string">'data:'</span> <span class="token operator">+</span> <span class="token string">'定时消息'</span> <span class="token operator">+</span> <span class="token string">'\n\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">EventSource 事件流格式为普通的 UTF-8 字符串即可，每条消息后面跟着一个<code>\n</code>来做分隔符，然后按照下面规范来规定字符串内容：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">注释行：以<code>:</code>开头的内容是注释行，会被忽略，定时发送一条注释行可以用来防止连接超时；</li>
<li style="font-size: 20px; line-height: 38px;">字段：字段由字段名和字段值按照<code>字段名: 字段值</code>的规范组成，规范中的字段有：<code>event</code>、<code>data</code>、<code>id</code>和<code>retry</code>。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">举例来说明：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">下面是未命名事件：</p>
</div><div class="cl-preview-section"><pre><code>: this is a test stream

data: some text

data: another message
data: with two lines
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">命名事件：</p>
</div><div class="cl-preview-section"><pre><code>event: ping
data: {"username": "bobby", "time": "02:33:48"}
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">也可以在一个事件流中同时使用命名事件和未命名事件：</p>
</div><div class="cl-preview-section"><pre><code>event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

data: Here's a system message of some kind that will get used
data: to accomplish some task.
</code></pre>
</div><div class="cl-preview-section"><h3 id="给-contenbase-增加-watch-功能">给 contenBase 增加 watch 功能</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">先来看下 webpack-dev-server 是怎么实现的。首先在 Server 端使用<a href="https://www.npmjs.com/package/chokidar">chokidar</a>添加 contentBase 路径的监听，发生<code>change</code>事件则触发<code>sockWrite</code>发送一个<code>content-changed</code>事件，这部分代码在<code>webpack-dev-server/lib/Server.js</code>中：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// webpack-dev-server/lib/Server.js</span>
<span class="token keyword">const</span> watcher <span class="token operator">=</span> chokidar<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span>watchPath<span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span>

watcher<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">sockWrite</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sockets<span class="token punctuation">,</span> <span class="token string">'content-changed'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 下面是sockWrite的实现</span>
<span class="token function">sockWrite</span><span class="token punctuation">(</span>sockets<span class="token punctuation">,</span> type<span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    sockets<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>socket<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token comment">// socket来自于sockjs.createServer之后的connection实例</span>
        socket<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">webpack-dev-server 的 client 端，通过<code>sock = new SockJS(url)</code>之后，添加<code>sock.onmessage</code>监听，监听 Server 端消息，最终接收到的消息处理扔给了<code>webpack-dev-server/client-src/default/index.js</code>的<code>onSocketMsg</code>对象针对不同的消息进行处理。下面是<code>content-changed</code>消息的处理，在里面我们看到了执行的是<code>self.location.reload()</code>最终导致页面重载：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token string">'content-changed'</span><span class="token punctuation">:</span> <span class="token keyword">function</span> <span class="token function">contentChanged</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">'[WDS] Content base changed. Reloading...'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    self<span class="token punctuation">.</span>location<span class="token punctuation">.</span><span class="token function">reload</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">所以要在 webpack-hot-middleware 中实现 contentBase 的文件变化监听，然后通知 client 端页面重载，需要我们做两件事情：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">使用 chokidar 监听文件变化；</li>
<li style="font-size: 20px; line-height: 38px;">然后通过 webpack-hot-middleware 推送消息到 client 端，client 端接收到消息重新加载页面。</li>
</ol>
</div><div class="cl-preview-section"><h4 id="使用-chokidar-监听文件变化" style="font-size: 26px;">使用 chokidar 监听文件变化</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这部分代码使用 chokidar 实现的，比较简单，直接放实现代码：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span>devServerConfig<span class="token punctuation">.</span>watchContentBase<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 判断watchoptions</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span>poll<span class="token punctuation">,</span> ignored<span class="token punctuation">}</span> <span class="token operator">=</span> devServerConfig<span class="token punctuation">.</span>watchOptions<span class="token punctuation">;</span>
    <span class="token keyword">const</span> usePolling <span class="token operator">=</span> poll <span class="token operator">?</span> <span class="token boolean">true</span> <span class="token punctuation">:</span> undefined<span class="token punctuation">;</span>
    <span class="token keyword">const</span> interval <span class="token operator">=</span> <span class="token keyword">typeof</span> poll <span class="token operator">===</span> <span class="token string">'number'</span> <span class="token operator">?</span> poll <span class="token punctuation">:</span> undefined<span class="token punctuation">;</span>
    <span class="token comment">// 这个是chokidar的options，参考它的文档</span>
    <span class="token keyword">const</span> options <span class="token operator">=</span> <span class="token punctuation">{</span>
        ignoreInitial<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
        persistent<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
        followSymlinks<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
        atomic<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
        alwaysStat<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
        ignorePermissionErrors<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
        ignored<span class="token punctuation">,</span>
        usePolling<span class="token punctuation">,</span>
        interval
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword">function</span> <span class="token function">_watch</span><span class="token punctuation">(</span>watchPath<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> watcher <span class="token operator">=</span> chokidar<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span>watchPath<span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span>
        watcher<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token comment">// TODO 这里需要我们新一步编写代码实现发送Server消息</span>
            <span class="token comment">// ...</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>Array<span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>contentBase<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        contentBase<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>item <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token function">_watch</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>contentBase <span class="token operator">&amp;&amp;</span> <span class="token keyword">typeof</span> contentBase <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">_watch</span><span class="token punctuation">(</span>contentBase<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><h4 id="利用-webpack-hot-middleware-实现页面重载" style="font-size: 26px;">利用 webpack-hot-middleware 实现页面重载</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 webpack-hot-middleware 中是使用 EventSource 来通信的，同时它在客户端和 Server 端都暴漏了接口可以让我们实现 Server 端到客户端的通信。我们判断出来 contentBase 内容发生变化之后，应该像 webpack-dev-server 实现一样，发送个让页面重新加载（reload）的事件。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">可以在<code>webpack-hot-middleware/middleware.js</code>中找到 webpack-hot-middleware 实际返回的是一个<code>middleware</code>的函数，而这个函数有个<code>publish</code>方法，这个方法就是利用<code>eventStream</code>对象发送 Server 消息给 client 的：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// webpack-hot-middleware/middleware.js</span>
middleware<span class="token punctuation">.</span><span class="token function-variable function">publish</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>closed<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
    eventStream<span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这样，我们只需将 webpack-hot-middleware 的返回赋值给一个<code>webpackHotMiddlewareInstance</code>对象，后面就可以在上面的 chokidar 代码中的<code>_watch</code>函数内直接使用<code>webpackHotMiddlewareInstance.publish</code>发送消息了：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> webpackHotMiddlewareInstance <span class="token operator">=</span> <span class="token function">webpackHotMiddleware</span><span class="token punctuation">(</span>compiler<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    log<span class="token punctuation">:</span> console<span class="token punctuation">.</span>log<span class="token punctuation">,</span>
    path<span class="token punctuation">:</span> <span class="token string">'/__webpack_hmr'</span><span class="token punctuation">,</span>
    heartbeat<span class="token punctuation">:</span> <span class="token number">10</span> <span class="token operator">*</span> <span class="token number">1000</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>webpackHotMiddlewareInstance<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 忽略其他代码</span>
<span class="token keyword">function</span> <span class="token function">_watch</span><span class="token punctuation">(</span>watchPath<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> watcher <span class="token operator">=</span> chokidar<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span>watchPath<span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span>
    watcher<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token comment">// 使用webpackHotMiddlewareInstance发送reload消息给client</span>
        <span class="token comment">// client接收消息后reload页面</span>
        webpackHotMiddlewareInstance<span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span><span class="token punctuation">{</span>action<span class="token punctuation">:</span> <span class="token string">'reload'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上面的代码发送了一个<code>{action: 'reload'}</code>的消息，那么我们接下来需要做的就是让 client 端页面处理这个消息，我们可以在<code>webpack-hot-middleware/client.js</code>中找到下面的代码：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// webpack-hot-middleware/client.js</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    subscribeAll<span class="token punctuation">:</span> <span class="token keyword">function</span> <span class="token function">subscribeAll</span><span class="token punctuation">(</span>handler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        subscribeAllHandler <span class="token operator">=</span> handler<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    subscribe<span class="token punctuation">:</span> <span class="token keyword">function</span> <span class="token function">subscribe</span><span class="token punctuation">(</span>handler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        customHandler <span class="token operator">=</span> handler<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    useCustomOverlay<span class="token punctuation">:</span> <span class="token keyword">function</span> <span class="token function">useCustomOverlay</span><span class="token punctuation">(</span>customOverlay<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>reporter<span class="token punctuation">)</span> reporter<span class="token punctuation">.</span><span class="token function">useCustomOverlay</span><span class="token punctuation">(</span>customOverlay<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    setOptionsAndConnect<span class="token punctuation">:</span> setOptionsAndConnect
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">说明这个<code>client.js</code>提供了<code>subscribeAll</code>和<code>subscribe</code>这两个跟消息订阅相关的接口函数，那么我们再顺藤摸瓜看下<code>subscribeAllHandler</code>和<code>customHandler</code>究竟是什么，继续在<code>client.js</code>中查找，找到了<code>processMessage</code>这个方法：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// webpack-hot-middleware/client.js</span>
<span class="token keyword">var</span> customHandler<span class="token punctuation">;</span>
<span class="token keyword">var</span> subscribeAllHandler<span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">processMessage</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">switch</span> <span class="token punctuation">(</span>obj<span class="token punctuation">.</span>action<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">//....忽略其他的action</span>
        <span class="token keyword">default</span><span class="token punctuation">:</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>customHandler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token function">customHandler</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>subscribeAllHandler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">subscribeAllHandler</span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这个方法就是处理接收到的 Server 端消息。最终所有的消息都扔给了<code>subscribeAllHandler</code>。而存在<code>obj.action</code>内的消息，如果<code>action</code>值不在<code>switch</code>分支，最后会进入<code>default</code>交给<code>customHandler</code>处理。<code>switch</code>这里只处理了<code>building</code>、<code>built</code>、<code>sync</code>3 个<code>action</code>，知道这些之后，我们就可以在 Webpack 的入口文件中，增加下面订阅 Server 消息的代码，然后<code>reload</code>页面：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">import</span> webpackHotMiddleware <span class="token keyword">from</span> <span class="token string">'webpack-hot-middleware/client?path=/__webpack_hmr&amp;timeout=20000'</span><span class="token punctuation">;</span>
<span class="token comment">// 通过subscribe方法来添加订阅</span>
webpackHotMiddleware<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">{</span>action<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 判断action===reload，则reload页面</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>action <span class="token operator">===</span> <span class="token string">'reload'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        location<span class="token punctuation">.</span><span class="token function">reload</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h3 id="实现-webpack-配置文件监控自动重启-dev-server">实现 Webpack 配置文件监控自动重启 dev-server</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">最后我们在一开始配置 Webpack 配置文件的时候，每次想看效果都要重启 dev-server，如果手动重启还是比较麻烦的，这里在介绍一个使用<a href="https://www.npmjs.com/package/nodemon">nodemon</a>的方式来监控<code>webpack.config.js</code>文件变化、自动重启 dev-server 的方式。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们可以在<code>package.json</code>中添加一个 NPM scripts：</p>
</div><div class="cl-preview-section"><pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">{</span>
    <span class="token string">"scripts"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
        <span class="token string">"start"</span><span class="token punctuation">:</span> <span class="token string">"nodemon --watch webpack.config.js --exec node ./dev-server.js"</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：除了这种方式还可以通过 Node.js 的<code>child_process.fork</code>来 fork 一个进程执行<code>dev-server.js</code>，同时使用 chokidar 来监控<code>webpack.config.js</code>变化，一旦变化则 kill 掉进程重新 fork 新的进程执行<code>dev-server.js</code>。</p>
</blockquote>
</div><div class="cl-preview-section"><h3 id="收尾整理">收尾整理</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">好了，到这里我们的 dev-server 就已经完成了，为了更好地被使用，我们也可以封装成一个 Node.js 模块，通过传入<code>webpack.config.js</code>的<code>devServer</code>配置来启动我们的 server。我们还可以<code>return</code>一个 Promise 对象，方便绑定回调，同时我们还需要使用<code>Compiler</code>的<code>done</code>hook 来监听 Webpack 打包的进展和结果，通过拿到的<code>stats</code>来展现打包结果。最后整个代码如下：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// server.js</span>
<span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> http <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// http-proxy-middleware</span>
<span class="token keyword">const</span> httpProxyMiddleware <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'http-proxy-middleware'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// mock-middleware</span>
<span class="token keyword">const</span> apiMockerMiddleware <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'mocker-api'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// hot</span>
<span class="token keyword">const</span> webpackHotMiddleware <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'webpack-hot-middleware'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// dev</span>
<span class="token keyword">const</span> webpackDevMiddleware <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'webpack-dev-middleware'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// watch file change</span>
<span class="token keyword">const</span> chokidar <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'chokidar'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> webpackConfig <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> devServerConfig <span class="token operator">=</span> webpackConfig<span class="token punctuation">.</span>devServer<span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span>port <span class="token operator">=</span> <span class="token number">3000</span><span class="token punctuation">,</span> host <span class="token operator">=</span> <span class="token string">'0.0.0.0'</span><span class="token punctuation">,</span> contentBase<span class="token punctuation">,</span> proxy<span class="token punctuation">,</span> mock<span class="token punctuation">}</span> <span class="token operator">=</span> devServerConfig<span class="token punctuation">;</span>
    <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 0. 实现static服务器</span>
    <span class="token comment">// 判断类型，如果是数组，那么就循环添加</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>Array<span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>contentBase<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        contentBase<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>item <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'*'</span><span class="token punctuation">,</span> express<span class="token punctuation">.</span><span class="token keyword">static</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>contentBase <span class="token operator">&amp;&amp;</span> <span class="token keyword">typeof</span> contentBase <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'*'</span><span class="token punctuation">,</span> express<span class="token punctuation">.</span><span class="token keyword">static</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>contentBase<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 1. proxy</span>
    Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>proxy<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>router <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>router<span class="token punctuation">,</span> <span class="token function">httpProxyMiddleware</span><span class="token punctuation">(</span>proxy<span class="token punctuation">[</span>router<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 2. mocker-api</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>mock<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 这里的mock是mocker-api配置文件路径</span>
        <span class="token function">apiMockerMiddleware</span><span class="token punctuation">(</span>app<span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>mock<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 3. webpack</span>

    <span class="token comment">// 3.1. 读取webpack.config.js文件</span>
    <span class="token keyword">const</span> webpack <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'webpack'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 3.2. 创建compiler对象</span>
    <span class="token keyword">const</span> compiler <span class="token operator">=</span> <span class="token function">webpack</span><span class="token punctuation">(</span>webpackConfig<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 3.3. 添加dev-middleware，使用compiler参数</span>
    app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>
        <span class="token function">webpackDevMiddleware</span><span class="token punctuation">(</span>compiler<span class="token punctuation">,</span> <span class="token punctuation">{</span>
            logLevel<span class="token punctuation">:</span> <span class="token string">'warn'</span><span class="token punctuation">,</span>
            publicPath<span class="token punctuation">:</span> webpackConfig<span class="token punctuation">.</span>output<span class="token punctuation">.</span>publicPath
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 3.4. 添加hot-middleware，使用compiler参数</span>
    <span class="token keyword">const</span> webpackHotMiddlewareInstance <span class="token operator">=</span> <span class="token function">webpackHotMiddleware</span><span class="token punctuation">(</span>compiler<span class="token punctuation">,</span> <span class="token punctuation">{</span>
        log<span class="token punctuation">:</span> console<span class="token punctuation">.</span>log<span class="token punctuation">,</span>
        path<span class="token punctuation">:</span> <span class="token string">'/__webpack_hmr'</span><span class="token punctuation">,</span>
        heartbeat<span class="token punctuation">:</span> <span class="token number">10</span> <span class="token operator">*</span> <span class="token number">1000</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>webpackHotMiddlewareInstance<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 4. watchContentBase</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>devServerConfig<span class="token punctuation">.</span>watchContentBase<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 判断watchoptions</span>
        <span class="token keyword">const</span> <span class="token punctuation">{</span>poll<span class="token punctuation">,</span> ignored<span class="token punctuation">}</span> <span class="token operator">=</span> devServerConfig<span class="token punctuation">.</span>watchOptions<span class="token punctuation">;</span>
        <span class="token keyword">const</span> usePolling <span class="token operator">=</span> poll <span class="token operator">?</span> <span class="token boolean">true</span> <span class="token punctuation">:</span> undefined<span class="token punctuation">;</span>
        <span class="token keyword">const</span> interval <span class="token operator">=</span> <span class="token keyword">typeof</span> poll <span class="token operator">===</span> <span class="token string">'number'</span> <span class="token operator">?</span> poll <span class="token punctuation">:</span> undefined<span class="token punctuation">;</span>
        <span class="token comment">// 这个是chokidar的options</span>
        <span class="token keyword">const</span> options <span class="token operator">=</span> <span class="token punctuation">{</span>
            ignoreInitial<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
            persistent<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
            followSymlinks<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
            atomic<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
            alwaysStat<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
            ignorePermissionErrors<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
            ignored<span class="token punctuation">,</span>
            usePolling<span class="token punctuation">,</span>
            interval
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
        <span class="token keyword">function</span> <span class="token function">_watch</span><span class="token punctuation">(</span>watchPath<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">const</span> watcher <span class="token operator">=</span> chokidar<span class="token punctuation">.</span><span class="token function">watch</span><span class="token punctuation">(</span>watchPath<span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span>
            watcher<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                <span class="token comment">// 使用webpackHotMiddlewareInstance发送reload消息给client</span>
                <span class="token comment">// client接收消息后reload页面</span>
                webpackHotMiddlewareInstance<span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span><span class="token punctuation">{</span>action<span class="token punctuation">:</span> <span class="token string">'reload'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>Array<span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>contentBase<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            contentBase<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>item <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                <span class="token function">_watch</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>contentBase <span class="token operator">&amp;&amp;</span> <span class="token keyword">typeof</span> contentBase <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token function">_watch</span><span class="token punctuation">(</span>contentBase<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 6. 增加before支持</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> devServerConfig<span class="token punctuation">.</span>before <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        devServerConfig<span class="token punctuation">.</span><span class="token function">before</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 最后，创建server，并且输出地址</span>
    <span class="token comment">// const server = http.createServer(app);</span>
    <span class="token comment">// server.listen(port, () =&gt; {</span>
    <span class="token comment">//     console.log(`Listening on http://localhost:${port}`);</span>
    <span class="token comment">// });</span>

    <span class="token keyword">const</span> ID <span class="token operator">=</span> <span class="token string">'diy-dev-server'</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> reject<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> server <span class="token operator">=</span> http<span class="token punctuation">.</span><span class="token function">createServer</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">const</span> urlForBrowser <span class="token operator">=</span> <span class="token template-string"><span class="token string">`http://</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>host<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>port<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span>
        <span class="token comment">// 第一次flag，第一次编译就输出log</span>
        <span class="token keyword">let</span> isFirstCompile <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
        compiler<span class="token punctuation">.</span>hooks<span class="token punctuation">.</span>done<span class="token punctuation">.</span><span class="token function">tap</span><span class="token punctuation">(</span>ID<span class="token punctuation">,</span> stats <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>stats<span class="token punctuation">.</span><span class="token function">hasErrors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">const</span> info <span class="token operator">=</span> stats<span class="token punctuation">.</span><span class="token function">toJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>info<span class="token punctuation">.</span>errors<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">reject</span><span class="token punctuation">(</span>stats<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">return</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>stats<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">{</span>children<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> modules<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> colors<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>isFirstCompile<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`    DevServer running at: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>urlForBrowser<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
                    server<span class="token punctuation">,</span>
                    url<span class="token punctuation">:</span> urlForBrowser
                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        server<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span>port<span class="token punctuation">,</span> host<span class="token punctuation">,</span> err <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token function">reject</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：测试的代码这里不再做讲解了，直接打开本篇文章的代码然后执行<code>npm start</code>查看我们的 dev-server 效果吧：</p>
<ol>
<li style="font-size: 20px; line-height: 38px;">HMR：修改<code>index.js</code>内容，就可以看到 HMR 效果了，比如把<code>app.style.background = '#99d';</code>修改成其它颜色；</li>
<li style="font-size: 20px; line-height: 38px;">contentBase 和 watch：修改<code>public/hello.js</code>内容，保存则会直接接收到 reload 消息，重载页面；</li>
<li style="font-size: 20px; line-height: 38px;">proxy：可以访问<code>http://localhost:3000/users/</code>查看效果;</li>
<li style="font-size: 20px; line-height: 38px;">mock：可以按照<code>mock/index.js</code>的配置访问各接口试下，比如：<code>http://localhost:3000/repos/hello</code>；</li>
<li style="font-size: 20px; line-height: 38px;">webpack.config.js watch：修改下配置文件，服务就重新启动了。</li>
</ol>
</blockquote>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">对于后端服务已经是 Express 或者有自己的后端逻辑需要 Express 来实现的时候，例如在我们项目中，后端服务器实际为 PHP+Smarty 模板的，我们就是用自定义的 Express 服务器实现一个 dev-server。它支持 Webpack-dev-server 的全部功能（本文内容就是其中一部功能），还能够利用 PHP <code>bin</code>命令来做 Smarty 模板数据 Mock 和模板渲染。本篇文章就是最简单地实现了 Webpack-dev-server 功能，并且我们还可以继续在本节源码技术上扩展自己的 Express 服务器功能。通过本文的内容，可以让我们更好地理解 Webpack-dev-server 和 HMR 内核实现，并且在实际项目中得到应用。</p>
</div></div>
            </div>
                            <!-- 买过的阅读 -->
                <div class="art-next-prev clearfix">
                                                                        <!-- 已买且开放 或者可以试读 -->
                            <a href="/read/29/article/289">
                                                    <div class="prev l clearfix">
                                <div class="icon l">
                                    <i class="imv2-arrow3_l"></i>
                                </div>
                                <p>
                                    实战：手写一个 prefetch-webpack-plugin 插件
                                </p>
                            </div>
                        </a>
                                                                                            <!-- 已买且开放 或者可以试读 -->
                            <a href="/read/29/article/291">
                                                    <div class="next r clearfix">
                                <p>
                                    实战：使用 Stats 数据结构生成 Webpack 构建报告
                                </p>
                                <div class="icon r">
                                    <i class="imv2-arrow3_r"></i>
                                </div>

                            </div>
                        </a>
                                    </div>
                    </div>
        <div class="comments-con js-comments-con" id="coments_con">     <div class="number">精选留言 <span class="js-number">0</span></div>     <div class="comments">         <div class="input-fake js-showcommentModal">             欢迎在这里发表留言，作者筛选后可公开显示         </div>                      <div class="noData">                 <p>                     <i class="imv2-error_c"></i>                 </p>                 <p>目前暂无任何讨论</p>             </div>              </div>  </div>



    </div>
    
    
    

</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>